package com.opensource.component.extend.point.support;

import com.opensource.component.extend.point.anntation.EnableSpecificExtend;
import com.opensource.component.extend.point.anntation.SpecificExtend;
import com.opensource.component.extend.point.metadata.SpecificExtendMetadata;
import com.opensource.component.extend.point.metadata.SpecificExtendMetadataHelper;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.jetbrains.annotations.NotNull;
import org.springframework.beans.factory.BeanClassLoaderAware;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.BeanDefinitionHolder;
import org.springframework.beans.factory.support.BeanDefinitionReaderUtils;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
import org.springframework.core.type.AnnotationMetadata;
import org.springframework.util.ClassUtils;
import org.springframework.util.StringUtils;

import java.beans.Introspector;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Set;

/**
 * RemoteClient注册器
 *
 * @author ZonLen since on 2021/6/29 下午7:50
 */
@Slf4j
public class ExtendPointRegistrar implements ImportBeanDefinitionRegistrar, BeanClassLoaderAware {

    private ClassLoader classLoader;

    @SneakyThrows
    @Override
    public void registerBeanDefinitions(@NotNull AnnotationMetadata metadata,
                                        @NotNull BeanDefinitionRegistry registry) {
        final ExtendPointScanner scanner = new ExtendPointScanner(registry);
        final List<String> basePackages = new ArrayList<>();
        final String[] basePackageStrs = (String[]) metadata.getAnnotationAttributes(EnableSpecificExtend.class.getName()).get("basePackages");
        basePackages.addAll(Arrays.asList(basePackageStrs));
        for (String basePackage : basePackages) {
            Set<BeanDefinition> candidateComponents = scanner.findCandidateComponents(basePackage);
            for (BeanDefinition candidateComponent : candidateComponents) {
                final String beanClassName = candidateComponent.getBeanClassName();
                if (!StringUtils.hasText(beanClassName)) {
                    continue;
                }
                final Class<?> clazz = ClassUtils.forName(beanClassName, classLoader);
                if (clazz.getSuperclass() == Object.class) {
                    continue;
                }
                final SpecificExtend specificExtend = clazz.getAnnotation(SpecificExtend.class);
                if (specificExtend != null) {
                    if (!specificExtend.extendBeanClass().isAssignableFrom(clazz.getSuperclass())){
                        throw new RuntimeException("Class ["+clazz.getName()+"] extends super class non match @SpecificExtend extendBeanClass type");
                    }
                    SpecificExtendMetadataHelper.populateExtendPoint(clazz, specificExtend.extendBeanClass(), candidateComponent);
                }
            }
            final List<SpecificExtendMetadata> specificExtendMetadataList = SpecificExtendMetadataHelper.extendPointMetadata();
            if (specificExtendMetadataList.isEmpty()) {
                return;
            }
            for (SpecificExtendMetadata specificExtendMetadata : specificExtendMetadataList) {
                registerRemoteClient(registry, specificExtendMetadata.getExtendBeanDefinition());
            }
        }
    }

    @Override
    public void setBeanClassLoader(ClassLoader classLoader) {
        this.classLoader = classLoader;
    }


    private void registerRemoteClient(BeanDefinitionRegistry registry,
                                      BeanDefinition candidateComponent) {
        String className = candidateComponent.getBeanClassName();
        candidateComponent.setPrimary(true);
        BeanDefinitionHolder holder = new BeanDefinitionHolder(candidateComponent,
                generateBeanName(className));
        BeanDefinitionReaderUtils.registerBeanDefinition(holder, registry);
    }


    private String generateBeanName(String beanClassName) {
        String shortClassName = ClassUtils.getShortName(beanClassName);
        return Introspector.decapitalize(shortClassName);
    }

}
